<?php

Raise::load('RaiseObject');
Raise::load('core.helper.config.RaiseSettings');
Raise::load('core.net.http.RaiseHttpResponse');
Raise::load('core.net.http.RaiseHttpRequest');
Raise::load('core.net.http.RaiseHttpClient');
Raise::load('core.plugin.RaisePluginManager');
Raise::load('core.session.RaiseSession');
Raise::load('core.security.RaiseCsrfProtect');
Raise::load('core.exception.RaiseDefaultExceptionHandler');
Raise::load('core.session.auth.RaiseUserFactory');
Raise::load('core.io.file.RaisePath');
Raise::load('core.RaiseAppPackageLoader');
Raise::load('core.datetime.RaiseDateTime');
Raise::load('core.datetime.RaiseTimeSpan');
Raise::load('core.collection.RaiseCollection');
Raise::load('core.helper.routing.RaiseUrlRouteManager');
Raise::load('core.action.RaiseActionManager');
Raise::load('core.text.regex.RaiseRegex');
Raise::load('core.i18n.RaiseTranslator');

/**
 * RaiseApplication class
 * entry point for the application to work.
 *
 * @author Sam-Mauris Yong / hellclanner at live dot com
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package phpraise.core
 * @since 1.2
 * @todo reduce dependencies
 */
class RaiseApplication extends RaiseObject {

    /**
     * The name of this RaiseApplication
     * Allowed characters include: alpha numeric, dots, hypens
     * The name must start and end with alpha numeric characters
     * Length of the name must be between 6 to 30 allowed characters.
     * The allowed characters can be expressed as regular expression: ^[a-zA-Z0-9]{1}[a-zA-Z0-9.-]{4,28}[a-zA-Z0-9]{1}$
     * @var string
     */
    private $name;

    /**
     * The configuration file in use
     * @var RaiseSettings
     */
    private $settings;

    /**
     * The HTTP response of the application to the request.
     * @var RaiseHttpResponse
     */
    private $httpResponse;

    /**
     * The HTTP request to the application
     * @var RaiseHttpRequest
     */
    private $httpRequest;

    /**
     * The client accessing the application via HTTP
     * @var RaiseHttpClient
     */
    private $httpClient;

    /**
     * The RaisePluginManager handling plugins in the application
     * @var RaisePluginManager
     */
    private $plugin;

    /**
     * The Session used by this application
     * @var RaiseSession
     */
    private $session;

    /**
     * The RaiseCsrfProtect that will provide CSRF protection for the application
     * @var RaiseCsrfProtect
     */
    private $csrfProtect;

    /**
     * The RaiseAccessPermissions that checks for access and rules of users and application resources
     * @var RaiseAccessPermissions
     */
    private $accessControl;

    /**
     * The RaiseDefaultExceptionHandler that will handle all exceptions in this application that were not caught
     * @var RaiseDefaultExceptionHandler
     */
    private $exceptionHandler;
    
    /**
     * The current locale setting set for the application
     * @var RaiseLocale
     */
    private $locale;
    
    /**
     * The RaiseTranslator that will handle all translations
     * @var RaiseTranslator 
     */
    private $translator;

    /**
     * Creates the application object with the application name.
     * @param string|RaiseSettings $name The name of the application or the RaiseSettings to use. See $this->name()
     */
    function __construct($name) {
        if ($name instanceof RaiseSettings) {
            $this->settings = $name;
        } else {
            $this->name($name);
        }
    }

    /**
     * Get the name of the RaiseApplication
     * Allowed characters include: alpha numeric, dots, hypens. The name must start and end with alpha numeric characters.
     * Length of the name must be between 4 to 30 allowed characters.
     * The allowed characters can be expressed as regular expression: ^[a-zA-Z0-9]{1}[a-zA-Z0-9.-]{4,28}[a-zA-Z0-9]{1}$
     * @param string $n (optional) This can be only called in the constructor to validate and set the name.
     * @return string
     */
    public function name($n = null) {
        if (func_num_args() == 1) {
            if ($n !== null) {
                $i = preg_match('`^[a-zA-Z0-9]{1}[a-zA-Z0-9.-]{2,28}[a-zA-Z0-9]{1}$`is', $n);
                if ($i == 0) {
                    throw new RaiseInvalidArgumentException('Invalid RaiseApplication name entered. RaiseApplication name must match the regular expression "^[a-zA-Z0-9]{1}[a-zA-Z0-9.-]{4,28}[a-zA-Z0-9]{1}$"');
                }
            }
            $this->name = $n;
        }
        return $this->name;
    }

    /**
     * Get the RaiseHttpResponse to the RaiseHttpRequest
     * @return RaiseHttpResponse
     */
    public function response() {
        return $this->httpResponse;
    }

    /**
     * Get the RaiseHttpRequest that is received by this application
     * @return RaiseHttpRequest
     */
    public function request() {
        return $this->httpRequest;
    }

    /**
     * Get the RaiseHttpClient that is accessing this application via HTTP
     * @return RaiseHttpClient
     */
    public function client() {
        return $this->httpClient;
    }

    /**
     * Get the RaisePluginManager that is handling plugins for this application
     * @return RaisePluginManager
     */
    public function plugin() {
        return $this->plugin;
    }

    /**
     * Get the RaiseSession that is currently used by the application
     * @return RaiseSession
     */
    public function session() {
        return $this->session;
    }
    
    /**
     *  Get or set the permission checking RaiseAccessPermissions object for this application
     * @param RaiseAccessPermissions $ac
     * @return RaiseAccessPermissions 
     */
    public function accessControl($ac = null){
        if($ac instanceof RaiseAccessPermissions){
            $this->accessControl = $ac;
        }
        return $this->accessControl;
    }

    /**
     * Get the identifier of the user who is currently accessing the application
     * @return mixed
     */
    public function user($user = null) {
        static $factory = false;
        if (!$factory) {
            $factory = new RaiseUserFactory($this->session);
        }
        if(func_num_args() == 1){
            $factory->user($user);
        }
        return $factory->user();
    }

    /**
     * Get the RaiseExceptionHandler that will handle all exceptions in this application
     * @return RaiseExceptionHandler
     */
    public function exceptionHandler() {
        return $this->exceptionHandler;
    }
    
    /**
     * Get the translator for this application
     * @return RaiseTranslator
     */
    public function translator(){
       return $this->translator; 
    }

    /**
     * Get the RaiseCsrfProtect that provides CSRF protection for this application
     * @return RaiseCsrfProtect
     */
    public function csrfProtect() {
        return $this->csrfProtect;
    }

    /**
     * Return the settings for the application
     * @return RaiseSettings The settings used for this application
     */
    public function settings() {
        return $this->settings;
    }

    /**
     * Get the application root path
     * @return string
     */
    public function rootPath() {
        static $appRoot = null;
        if (!$appRoot) {
            $appRoot = RaisePath::path($_SERVER['SCRIPT_FILENAME']);
        }
        return $appRoot;
    }
    
    /**
     * Execute and run the application
     */
    public function execute(){
        $this->initialize();
        $this->doRouting();
    }
    
    /**
     * Load the settings from the configuration folder.
     */
    private function loadSettings(){
        $filename = 'app';
        if(RaiseServer::environment('PHPRAISE_ENV')){
            $filename = 'app.' . RaiseServer::environment('PHPRAISE_ENV');
        }
        $configPath = RaisePath::combine($_SERVER['SCRIPT_FILENAME'], '../raise/config/', $filename . '.ini');
        $this->settings = new RaiseSettings($configPath);
    }

    private function loadPackages() {

        $loader = new RaiseAppPackageLoader($this);
        $loader->loadPackage('app');
        $loader->loadPackage('dao');
        $loader->loadPackage('class');
        $loader->loadPackage('model');
        $loader->loadPackage('control');
        $loader->loadPackage('library');
        $loader->loadTemplates();
        /**
         * load the plugins
         */
        if ($this->rootPath()) {
            $this->plugin = new RaisePluginManager(RaisePath::combine($this->rootPath(), 'plugin'));
        } else {
            $this->plugin = new RaisePluginManager();
        }
    }

    /**
     * Initializes the application.
     */
    private function initialize() {
        /**
         * Set configuration file if no configuration file was set by programmer
         */
        $this->loadSettings();


       $this->loadPackages();
       
        /**
         * Perform i18n settings
         */
        
       $defaultLanguage = 'en';
       $defaultCountry = 'US';
       $targetLanguage = 'en';
       if ($this->settings()->get('i18n', 'country')){
           $defaultCountry = $this->settings()->get('i18n', 'country');
       }
       if ($this->settings()->get('i18n', 'defaultLanguage')){
           $defaultLanguage = $this->settings()->get('i18n', 'defaultLanguage');
       }
       if($this->settings()->get('i18n', 'language')) {
           $targetLanguage = $this->settings()->get('i18n', 'language');
       }
       $this->translator = new RaiseTranslator($this->rootPath(), $defaultLanguage, $targetLanguage);
       $this->locale(new RaiseLocale($targetLanguage, $defaultCountry));
       
       /**
        * Perform package loading
        */

        $this->httpRequest = RaiseHttpRequest::loadCurrent($this);
        $this->httpResponse = new RaiseHttpResponse($this);
        $this->httpClient = RaiseHttpClient::loadCurrent();

        /**
         * Load compatibility functions and tweaks
         * if required
         */
        if ($this->settings()->get('phpRaise', 'loadCompatibilityFunctions')) {
            Raise::load('core.RaiseCompatibility');
        }

        /**
         * Set the time zone as defined in settings
         */
        if ($this->settings()->get('phpRaise', 'timezone')) {
            date_default_timezone_set($this->settings()->get('phpRaise', 'timezone'));
        }

        /**
         * Set Session Garbage Collection settings
         */
        ini_set('session.gc_divisor', 100);
        if ($this->settings()->get('phpRaise', 'siteActivityLevel') > 0) {
            ini_set('session.gc_probability', ((11 - $this->settings()->get('siteActivityLevel')) / 10 * 100));
        }

        $this->exceptionHandler = new RaiseDefaultExceptionHandler($this);

        /**
         * Enable GZIP for HTTP
         * if set
         */
        if ($this->settings()->get('phpRaise', 'enableGzip')) {
            ini_set('zlib.output_compression', '1');
        }

        /**
         * Session start up procedure
         */
        if ($this->settings()->get('Session', 'store')) {
            $this->session = new RaiseSession(RaiseSession::loadStore($this->settings()));
        } else {
            $this->session = new RaiseSession();
        }
        $this->session->application($this);
        if ($this->settings()->get('Session', 'name')) {
            $this->session->name($this->settings()->get('Session', 'name'));
        }
        $timespan = $this->settings()->get('Session', 'timeSpan');
        if (!$timespan || $timespan <= 0) {
            $timespan = 3600;
        }
        $this->session->cookie()->expire(RaiseDateTime::now()->add(new RaiseTimeSpan($timespan)));
        $this->session->start();

        /**
         * Enable CSRF Protection if set
         */
        if ($this->settings()->get('phpRaise', 'enableCsrfProtection')) {
            $this->csrfProtect = new RaiseCsrfProtect($this);
            $this->csrfProtect->enable();
        }
    }
    
    /**
     * Get or set the locale used by the application
     * @param RaiseLocale $locale (optional) Set the locale used by the application
     * @return RaiseLocale
     */
    public function locale($locale = null){
        if($locale){
            setlocale(LC_ALL, $locale->__toString());
            $this->translator = new RaiseTranslator($this->rootPath(), $this->translator->originalLanguage(), $locale->__toString());
            $this->locale = $locale;
        }
        return $this->locale;
    }

    /**
     * Check whether the current user has permission to access the resource
     * @param string $resource
     */
    public function checkPermissions($resource) {
        $ok = true;
        
        if(!($this->accessControl instanceof RaiseAccessPermissions)){
            if (class_exists('AppAccess')) {
                $this->accessControl = new AppAccess();
                $this->accessControl->initialize();
            }else{
                $this->accessControl = new RaiseAccessReset();
                $this->accessControl->initialize();
            }
        }
        $ok = $this->accessControl->check($this->user(), $resource);
        if (!$ok) {
            throw new RaiseUserAccessDeniedException('No permission to access resource \'' . $resource . '\'.');
        }
    }

    /**
     * Do the neccessary routing functionalities and simulation
     * This method should only be called on the root index page.
     */
    public function doRouting() {
        $url = $this->request()->get($this->settings()->get('phpRaise', 'enableRewrite') ? RaiseUrlRouteManager::KEY : 'p');
        if ($url == null) {
            $url == '';
        }
        $routes = RaiseUrlRouteManager::routes();

        foreach ($routes as $route) {
            // check whether HTTP method matches for RESTful routing
            if($route->httpMethod() == $this->request()->method()){
                $t = new RaiseTemplate($route->rewrite());
                $tokens = $t->tokens();
                foreach ($tokens as $a) {
                    $v = RaiseRegex::escape($route->params()->get($a));
                    if (!$v) {
                        $v = '(*)';
                    }
                    $t->fields()->add($a, '(?P<' . $a . '>' . $v . ')');
                }
                $matches = array();
                $i = preg_match('`^' . $t->parse() . '([/]{0,1})$`is', $url, $matches);
                if ($i) {
                    $params = array();
                    foreach ($tokens as $a) {
                        $params[$a] = $matches[$a];
                    }
                    $params = array_merge($params, $this->request()->get()->toArray());
                    if($route->httpMethod() == RaiseHttpMethod::POST){
                        $params = array_merge($params, $this->request()->post()->toArray());
                    }
                    $cls = $route->actual();
                    $manager = new RaiseActionManager($this);
                    if(is_string($cls)){
                        $action = $manager->loadAction($cls, $params);
                    }else{
                        $action = $cls;
                    }
                    if($action){
                        $manager->doAction($action);
                        return;
                    }else{
                        throw new RaiseInvalidRequestException('The RaiseAction object of class name \'' . $cls . '\' was not found.');
                        break;
                    }
                }
            }
        }

        $ex = new RaisePageNotFoundException('The requested URL \'' . $url . '\' is not found.');
        $ex->setContext($this);
        throw $ex;
    }
    
    public function __destruct() {
        parent::__destruct();
        setlocale(LC_ALL, ini_get('intl.default_locale'));
    }

    
}
